package ;

import JagTileInfo;
import Math;



/**
 * ...
 * @author m
 */

class JagScene extends SceneView
{
	var mMaxItems: Int;
	
	var mCounters: JagCounters;
   
	
	public function new() 
	{
		super();

		mCounters = new JagCounters();
	}
	
	
	override function Reset()
	{		
		super.Reset();

		mCounters.Reset();
		
		mMaxItems = 0;		
	}
	
	
	// level generation
	
	override function GenerateLevel()
	{	
		// this will call standard handling
		super.GenerateLevel();		
		
		//trace("JagScene::GenerateLevel");
		
		SetMaxItems(6);
		
		SetFieldSize(12, 16);
				
		// randomly generate the walls
		GenerateWallsMirrored();
		
		// fill the backgrounds
		for (r in 0...mRows) {
			for (c in 0...mCols) {
				SetBackgroundTile(new SceneCoord2d(r, c), JagTileInfo.Background1);
			}
		}
		
		// start
		mCounters.Time.Start(62*60*1000);
	}
	
	
	private function GenerateWalls(rows: Int, cols: Int)
	{
		var nWalls = (rows + cols);
		for (i in 0...nWalls) {
			var r = Math.floor(Math.random() * rows);
			var c = Math.floor(Math.random() * cols);
			SetWallTile(new SceneCoord2d(r, c), JagTileInfo.Wall1);
		}
	}
	
	
	private function GenerateWallsMirrored()
	{
		var rows2 = Math.floor(mRows / 2);
		var cols2 = Math.floor(mCols / 2);

		var nWalls = rows2 + cols2;
		for (i in 0...nWalls) {
			var r = Math.floor(Math.random() * rows2);
			var c = Math.floor(Math.random() * cols2);
			SetWallTile(new SceneCoord2d(r, c), JagTileInfo.Wall1);
			SetWallTile(new SceneCoord2d(r, mCols-c-1), JagTileInfo.Wall1);
			SetWallTile(new SceneCoord2d(mRows-r-1, c), JagTileInfo.Wall1);
			SetWallTile(new SceneCoord2d(mRows-r-1, mCols-c-1), JagTileInfo.Wall1);
		}		
	}
	
	
	private function SetMaxItems(v: Int)
	{
		mMaxItems = v;
		
		mCounters.SetMaxItems(v);
	}
	
	
	// tile info
	
	override function CreateTileInfo(): TileInfo
	{
		return new JagTileInfo();
	}
	
	
	inline public function GetInfo(row: Int, col: Int): JagTileInfo
	{
		return cast(mField[row][col], JagTileInfo);
	}

	
	inline public function GetInfoPos(pos: SceneCoord2d): JagTileInfo
	{
		return cast(mField[pos.row][pos.col], JagTileInfo);
	}
	
	
	// creation
		
	private function SetBackgroundTile(pos: SceneCoord2d, typeInfo: Int)
	{
		mField[pos.row][pos.col].SetType(JagTileInfo.JAG_Background, true);		
	}

	
	private function SetWallTile(pos: SceneCoord2d, typeInfo: Int)
	{
		mField[pos.row][pos.col].SetType(JagTileInfo.JAG_Wall, true);		
	}
	
	
	private function CreateNewItem(pos: SceneCoord2d)
	{
		var itemId = Math.floor(Math.random() * mMaxItems) + JagTileInfo.Item1;
		
		var itemInfo = GetInfoPos(pos);
		
		itemInfo.CreateItem(itemId);
	}	
	
	
	private function CreateNewItems()
	{
		for (r in 0...mRows) {
			for (c in 0...mCols) {
				var pos = new SceneCoord2d(r, c);
				if (GetInfoPos(pos).IsFreeForItem()) {
					// generate new item
					CreateNewItem(pos);
				}
			}
		}	
	}
	
	
	private function SwapItems(pos1: SceneCoord2d, pos2: SceneCoord2d)
	{
		var itemInfo1 = GetInfoPos(pos1);
		var itemInfo2 = GetInfoPos(pos2);
		
		itemInfo1.MoveTo(pos2);
		itemInfo2.MoveTo(pos1);
		mField[pos2.row][pos2.col] = itemInfo1;
		mField[pos1.row][pos1.col] = itemInfo2;		
	}	

	
	private function MoveItem(posFrom: SceneCoord2d, posTo: SceneCoord2d)
	{
		var itemInfo = GetInfoPos(posFrom);
		
		itemInfo.MoveTo(posTo);
		mField[posTo.row][posTo.col] = itemInfo;
		mField[posFrom.row][posFrom.col] = new JagTileInfo();
	}		
	
	// destruction of the items
	
	
	// kills the item (scheduling its removal)
	private function KillItem(pos: SceneCoord2d)
	{
		var itemInfo = GetInfoPos(pos);
	
		itemInfo.KillItem();
	}

	
	// finally removes the item and frees the place
	private function RemoveItem(pos: SceneCoord2d)
	{
		var itemInfo = GetInfoPos(pos);
		
		itemInfo.SetType(JagTileInfo.JAG_Item, false);
	}
	
	
	private function RemoveItemsInRow(row: Int, col: Int, cnt: Int)
	{
		for (c in col...col + cnt) {
			GetInfo(row, c).fRemove = true;
		}
	}

	
	private function RemoveItemsInColumn(row: Int, col: Int, cnt: Int)
	{
		for (r in row...row + cnt) {
			GetInfo(r, col).fRemove = true;
		}
	}

	
	private function CheckRemoveItemsInRow(row: Int, from: Int, to: Int, remove: Bool): Bool
	{
		var result = false;

		var j = to;
		while (j >= from && j >= 2) {
			var info = GetInfo(row, j);
			if (info.CanBeRemoved() == false) {
				j--;
				continue;
			}
			
			// only 3 and more
			var id = info.GetId();
			var cnt = 1;
			while (j-- > 0) {
				var pi1 = GetInfo(row, j);
				if (pi1.CanBeRemoved() == false || pi1.GetId() != id)
					break;
				cnt++;
			}
			j++;

			// found 3 or more items
			if (cnt >= 3)
			{
				// schedule for deletion
				if (remove)
					RemoveItemsInRow(row, j, cnt);
					
				result = true;
			}		

			j--;
		}

		return result;
	}


	private function CheckRemoveItemsInColumn(col: Int, from: Int, to: Int, remove: Bool): Bool
	{
		var result = false;

		var j = to;
		while (j >= from && j >= 2) {
			var info = GetInfo(j, col);
			if (info.CanBeRemoved() == false) {
				j--;
				continue;
			}
			
			// only 3 and more
			var id = info.GetId();
			var cnt = 1;
			while (j-- > 0) {
				var pi1 = GetInfo(j, col);
				if (pi1.CanBeRemoved() == false || pi1.GetId() != id)
					break;
				cnt++;
			}
			j++;

			// found 3 or more items
			if (cnt >= 3)
			{
				// schedule for deletion
				if (remove)
					RemoveItemsInColumn(j, col, cnt);
					
				result = true;
			}		

			j--;
		}

		return result;
	}
	
	
	// checks and schedules removing contiguous items
	// returns true if something was removed, else returns false
	private function CheckRemoveItems(): Bool
	{
		// we're checking from top-right to bottom-left
		var result = false;

		// horizontal check
		for (i in 0...mRows) {
			if (CheckRemoveItemsInRow(i, 0, mCols-1, true)) {
				result = true;				
			}
		}

		// vertical check
		for (i in 0...mCols) {
			if (CheckRemoveItemsInColumn(i, 0, mRows-1, true)) {
				result = true;
			}
		}
		
		// schedule removal of the dead objects and count score
		var score: Int = 0;
		
		for (r in 0...mRows) {
			for (c in 0...mCols) {
				var pos = new SceneCoord2d(r, c);
				var itemInfo = GetInfoPos(pos);
				
				// if scheduled for death: kill
				if (itemInfo.fRemove == true) {
					KillItem(pos);
					
					score++;
					
					mCounters.CountItem(itemInfo.GetId(), 1);
				}
				
				// if dead already: remove
				if (itemInfo.IsDead()) {
					RemoveItem(pos);
				}
			}
		}
		
		// increase score counter
		if (result) {
			//trace("killed, score: " + score);
			if (score > 3) {
				mCounters.Score.Add((score-2) * score);
			} else {
				mCounters.Score.Add(score);
			}
		}
	
		return result;
	}
	
	
	// fall item logic
	
	private function CheckFallItems(): Bool
	{
		// we're checking from bottom-right to top-left
		var result = false;

		// horizontal check - row 0 is skipped for now
		var i: Int = mRows;
		while (--i > 0)
		{
			for (j in 0...mCols)
			{
				var pi = GetInfo(i, j);
				if (!pi.IsFreeForItem())
					continue;

				// check if something at the top
				pi = GetInfo(i - 1, j);
				if (pi == null)
					continue;
				if (pi.CanBeMoved()) {
					MoveItem(new SceneCoord2d(i - 1, j), new SceneCoord2d(i, j));
					result = true;
					continue;
				}
			
				// check top-left
				if (j > 0) {
					pi = GetInfo(i - 1, j - 1);
					if (pi == null)
						continue;
					if (pi.CanBeMoved()) {
						MoveItem(new SceneCoord2d(i - 1, j - 1), new SceneCoord2d(i, j));
						result = true;
						continue;
					}
				}

				// check top-right
				if (j < mCols - 1) {
					pi = GetInfo(i - 1, j + 1);
					if (pi == null)
						continue;
					if (pi.CanBeMoved() && !GetInfo(i, j + 1).IsFreeForItem()) {
						MoveItem(new SceneCoord2d(i - 1, j + 1), new SceneCoord2d(i, j));
						result = true;
						continue;				
					}
				}
			}
		}
		
		// check if some items still falling
		if (!result) {
			for (r in 0...mRows) {
				for (c in 0...mCols) {
					var itemInfo = GetInfo(r, c);
					if (itemInfo.IsMoving()) {
						//trace("moving");
						return true;
					}
				}
			}			
		}

		//trace("checkfall: " + result);
		return result;
	}
	
	
	// user input logic	
	
	override function OnFieldRelease(pos: SceneCoord2d)
	{
		var clickPos = GetFieldClickPos();
		
		if (!pos.IsValid() || !clickPos.IsValid())
			return;
				
		var dr = Math.abs(pos.row - clickPos.row);
		var dc = Math.abs(pos.col - clickPos.col);
		
		if (!((dr == 0 && dc == 1) || (dc == 0 && dr == 1)))
			return;
			
		var itemInfo1 = GetInfoPos(clickPos);
		var itemInfo2 = GetInfoPos(pos);
		
		if (!itemInfo1.CanBeMoved() || !itemInfo2.CanBeMoved())
			return;
			
		var id1 = itemInfo1.GetId();
		var id2 = itemInfo2.GetId();
			
		if (id1 == id2)
			return;
			
		// make fake change of the items and check for removal
		itemInfo1.SetId(id2);
		itemInfo2.SetId(id1);
					
		var b1 = CheckRemoveItemsInRow(pos.row, Math.floor(Math.max(0,pos.col-2)), Math.floor(Math.min(GetFieldCols()-1,pos.col+2)), false);
		var b2 = CheckRemoveItemsInColumn(pos.col, Math.floor(Math.max(0,pos.row-2)), Math.floor(Math.min(GetFieldRows()-1,pos.row+2)), false);
		var b3 = CheckRemoveItemsInRow(clickPos.row, Math.floor(Math.max(0,clickPos.col-2)), Math.floor(Math.min(GetFieldCols()-1,clickPos.col+2)), false);
		var b4 = CheckRemoveItemsInColumn(clickPos.col, Math.floor(Math.max(0, clickPos.row - 2)), Math.floor(Math.min(GetFieldRows() - 1, clickPos.row + 2)), false);
		
		// change back
		itemInfo1.SetId(id1);
		itemInfo2.SetId(id2);
		
		if (!(b1 || b2 || b3 || b4))
			return;
			
		//trace("action from " + GetFieldClickPos() + " to " + pos);
		
		// swap the items
		
		SwapItems(clickPos, pos);
	}
	
		
	// main logic loop
	
	override function UpdateLogic()
	{
		super.UpdateLogic();
		
		CheckRemoveItems();
		
		var fall = CheckFallItems();
				
		if (!fall) {
			CreateNewItems();
		}
	}
	
}